/*_############################################################################
  _## 
  _##  SNMP4J 2 - VariantVariable.java  
  _## 
  _##  Copyright (C) 2003-2011  Frank Fock and Jochen Katz (SNMP4J.org)
  _##  
  _##  Licensed under the Apache License, Version 2.0 (the "License");
  _##  you may not use this file except in compliance with the License.
  _##  You may obtain a copy of the License at
  _##  
  _##      http://www.apache.org/licenses/LICENSE-2.0
  _##  
  _##  Unless required by applicable law or agreed to in writing, software
  _##  distributed under the License is distributed on an "AS IS" BASIS,
  _##  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  _##  See the License for the specific language governing permissions and
  _##  limitations under the License.
  _##  
  _##########################################################################*/
package org.snmp4j.smi;

import org.snmp4j.asn1.BERInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * The <code>VariantVariable</code> provides a decorator for any type
 * of Variable instance, to be able to intercept or monitor variable
 * value modification by using a {@link VariantVariableCallback}.
 * <p>
 * This class will work for implementations that use {@link #getSyntax()}
 * method to determine the variables syntax. However "instanceof" will not
 * work.
 * <p>
 * In contrast to the native <code>Variable</code> implementations,
 * <code>VariantVariable</code> can be modified dynamically (i.e. while
 * a PDU is being BER encoded where this variable has been added to) without
 * causing BER encoding errors.
 *
 * @author Frank Fock
 * @version 1.8
 * @since 1.7
 */
public class VariantVariable extends AbstractVariable implements AssignableFromInteger, AssignableFromLong, AssignableFromString, AssignableFromByteArray {

    private static final long serialVersionUID = -3678564678835871188L;

    private Variable variable;
    private VariantVariableCallback callback;

    /**
     * Creates a variant variable wrapping the specified value.
     * @param initialVariable
     *    a <code>Variable</code>.
     */
    public VariantVariable(Variable initialVariable) {
        if (initialVariable == null) {
            throw new NullPointerException();
        }
        this.variable = initialVariable;
    }

    /**
     * Creates a variant variable wrapping the specified value and a callback
     * that monitors value modifications.
     * @param initialVariable
     *    a <code>Variable</code>.
     * @param callback
     *    a callback handler that is called before the value is to be modified
     *    and after it has been modified.
     */
    public VariantVariable(Variable initialVariable, VariantVariableCallback callback) {
        this(initialVariable);
        this.callback = callback;
    }

    public synchronized int compareTo(Variable o) {
        updateVariable();
        return variable.compareTo(o);
    }

    protected void updateVariable() {
        if (callback != null) {
            callback.updateVariable(this);
        }
    }

    protected void variableUpdated() {
        if (callback != null) {
            callback.variableUpdated(this);
        }
    }

    public synchronized void decodeBER(BERInputStream inputStream) throws IOException {
        variable.decodeBER(inputStream);
        variableUpdated();
    }

    public synchronized void encodeBER(OutputStream outputStream) throws IOException {
        updateVariable();
        variable.encodeBER(outputStream);
    }

    public synchronized void fromSubIndex(OID subIndex, boolean impliedLength) {
        variable.fromSubIndex(subIndex, impliedLength);
        variableUpdated();
    }

    public synchronized int getBERLength() {
        updateVariable();
        return variable.getBERLength();
    }

    public int getSyntax() {
        return variable.getSyntax();
    }

    public synchronized int toInt() {
        updateVariable();
        return variable.toInt();
    }

    public synchronized long toLong() {
        updateVariable();
        return variable.toLong();
    }

    public synchronized byte[] toByteArray() {
        updateVariable();
        if (variable instanceof AssignableFromByteArray) {
            return ((AssignableFromByteArray) variable).toByteArray();
        }
        throw new UnsupportedOperationException();
    }

    public synchronized OID toSubIndex(boolean impliedLength) {
        updateVariable();
        return variable.toSubIndex(impliedLength);
    }

    public synchronized boolean equals(Object o) {
        updateVariable();
        return variable.equals(o);
    }

    public synchronized int hashCode() {
        updateVariable();
        return variable.hashCode();
    }

    public synchronized String toString() {
        updateVariable();
        return variable.toString();
    }

    public Object clone() {
        updateVariable();
        return new VariantVariable((Variable) variable.clone());
    }

    public synchronized void setValue(int value) {
        if (variable instanceof AssignableFromInteger) {
            ((AssignableFromInteger) variable).setValue(value);
        } else {
            throw new ClassCastException("An integer value cannot be assigned to " + variable);
        }
    }

    public synchronized void setValue(long value) {
        if (variable instanceof AssignableFromLong) {
            ((AssignableFromLong) variable).setValue(value);
        } else {
            throw new ClassCastException("A long value cannot be assigned to " + variable);
        }
    }

    public synchronized void setValue(OctetString value) {
        if (variable instanceof AssignableFromByteArray) {
            ((AssignableFromByteArray) variable).setValue(value.getValue());
        } else {
            throw new ClassCastException("An OctetString value cannot be assigned to " + variable);
        }
    }

    public synchronized void setValue(byte[] value) {
        if (variable instanceof AssignableFromByteArray) {
            ((AssignableFromByteArray) variable).setValue(value);
        } else {
            throw new ClassCastException("A byte array value cannot be assigned to " + variable);
        }
    }

    public synchronized void setValue(String value) {
        if (variable instanceof AssignableFromString) {
            ((AssignableFromString) variable).setValue(value);
        } else {
            throw new ClassCastException("A string value cannot be assigned to " + variable);
        }
    }

    /**
     * Returns the typed variable that holds the wrapped value.
     * @return
     *    a Variable instance.
     */
    public Variable getVariable() {
        return variable;
    }

    public boolean isDynamic() {
        return true;
    }

}
